Chronos UnityAsset

December 26, 2022


2022/12/27現在 サポート終了によりUnityAsset Store から削除されているためアセットを取得することができなくなっています

Chronosを利用することで

  • ゲーム全体を遅く/速くする (グローバル
  • 特定のグループを持つゲームオブジェクトのみを遅く/早くする (ローカル
  • 特定のColliderに触れている間のみ特定の速度に変化させる (エリア

といったゲームオブジェクトそれぞれをカスタイマイズした時間管理ができるようになります。 (公式サイトの動画を見るのが一番わかり易いです) https://ludiq.io/chronos/features

最初にChronosのコンポーネント、実装がどのような形になっているかを見ていきます。

Timekeeper

まず、Chronosで時間を管理するには TimeKeeper と呼ばれるゲーム全体の管理者となるオブジェクトが必要です。 これはシングルトンオブジェクトでシーンに一つだけ存在します。

Timekeeperは Clock クラスを管理しています

Clockクラス

適用するTimeScaleの値を持ち、毎フレーム更新処理が呼び出され適用する時間を計算するクラス

つまり Clock クラスが実際の時間管理を行い、このクラスが管理しているGameObjectに対して変数に持っているTimeScaleの値を適用させています

Clockクラスには以下3種類存在

GlobalClock 複数のClock,Timeline を内部に保持する。string型のKeyを持つ。
LocalLock アタッチされているGameObjectのみを管理する。
AreaClock 球体のエリア以内に入ったTImelineを持つGameObjectのタイムスケールを変更する

各種Clockの説明は以下の図がわかりやすいです。

6F526AC40AC96FFD247D3844E5928AA3 TimekeeperがTopに存在し、GlobalClockの親子構造、一番下にGameObjectにアタッチされているTimelineが存在しています

例えば、上部にある Root のKeyを持つ Global Clock の一時停止処理を呼び出すと、その下についている全てのTimelineが一時停止(Timescale: 0)されます。

  • 敵だけを遅く/早くしたい場合は Enemies のKeyを持つGlobal Clock を取得してTImescale値を変更させる。
  • Playerだけ(一番右下)早くしたい場合は、PlayerについているLocalClock(Player)のTimescaleを変更する。

ということが出来ます。

各種構造化させておき、Timescaleをジャンルごとに自由に操れるのがChronosパッケージの強みです

Timeline

Chronosの時間操作を適用させたいGameObjectには必ずTimelineを付ける必要があります。

以下設定値

Mode string Local: LocalClockを検索して適用。Global: GlobalClockを検索して自分に適用
Global Clock string ModeがGlobalのときのみ有効。自分に適用するGlobalClockのKeyを指定
Rewindable bool 巻き戻しのサポート。公式の動画を見るのがわかりやすいです。。
RecordingDuration float ※Rewindable true時のみ。記録最大継続時間(秒)。値を大きくするとその分巻き戻し時間も増えるがより多くのメモリも必要とする
RecordingInterval float ※Rewindable true時のみ。スナップショットの記録間隔(秒)。値を低くするとその分メモリを消費します

時間を制御したいGameObjectにTimelineを付け、Mode, Global Clock を適切に設定するだけで動くようになります。

Timelineの中身を見るとわかりますが、Awakeメソッドで

  • Animator
  • Animation
  • AudioSource
  • NavMeshAgent
  • ParticleSystem
  • WindZone
  • Terrain
  • TrailRenderer
  • 各種Rigidbody

のコンポーネントが自分についているかGetComponentで調べ、それに合ったタイムラインを内部で生成しています

// 例えばAnimator
var animatorComponent = GetComponent<Animator>();
if (animator == null && animatorComponent != null)
{
		animator = new AnimatorTimeline(timeline, animatorComponent);
		animator.Initialize();
		components.Add(animator);
}
else if (animator != null && animatorComponent == null)
{
	animator = null;
}

// Animatorのspeedを変更するカスタムクラス
public class AnimatorTimeline : ComponentTimeline<Animator>
{
...
}

ComponentTimelineのジェネリッククラスを生成しているためつまり自分でもカスタムクラスを作成することが出来ます (これはまた別記事で)

GlobalClockの実装

試しにGlobalClockの実装を行ってみます。

2つのGameObjetを作成しました。それぞれ

  • Capsule : Animator
  • Cube : RigidBody.velocity

で動かしています。

この2つのGameObjectをChronosで制御してみます

兎にも角にもTImekeeperがいるため作成。 Create → TimeKeeper を選択

EF30345889F7649B44037E818CBB0B1A

そしてゲームオブジェクトにChronosの各種コンポーネントをつけることでそのオブジェクトの時間を管理します

作成されたTimekeeperGameObjectには2つのコンポーネントがついています

  • Timekeeper
  • Global Clock

5E37FD2443EEB6B8E49A7F08723F56BA

そこに2つのGlobalClockをアタッチさせて名前を

  • GlobalA → Capsuleの時間操作
  • GlobalB → Cubeの時間操作

としてみます。

ParentにはRootを選択して親子構造をつくります。

4A850DAD22322F52A08F5212FB2E8857

各GameObjectにTimelineを付けます Animatorの方はGroupA RigidBodyの方はGroupBoxとします BD671E7F28E89C91E7B92D06D4852139

GroupA の GlobalClock の TImescale を 0.1 にして再生してみます

GroupAのCapsuleがゆっっくりと移動するようになりました。

次にGroupBのTimescaleを2にしてみます 4DD2A58886797C8E85F44BAFB5DD33C3

速度が上がりました。

GroupClockではKey名でGorup化し、特定GroupのTimescaleを制御する手法を取っています。

KeyはStringですがEditor機能でサポートしているため打ち間違いによるヒューマンエラーが起きないような設計になっています

Group化しているため、同じGroupを持つオブジェクトはスピードが同期します

Local Clock

キューブの一つを Local にしてみます。 LocalClockをアタッチ

A341901F901F8817D5A8871FA7FBC27F

この状態で再生

手前のキューブはTimescaleが1のLocalなので特に動きが変わらなくなりました。

LocalClock は世界に一つしか無いGameObject、例えば Player や演出用の環境オブジェクトにつけるのが良さそうです。

スクリプトから変更

スクリプトから特定のGroupのTimescale値を変更してみます Timekeeper はシングルトンなため、TimekeeperからGroup名で検索しTimescaleを変更します

void Start()
{
		// GropuAを2バイの速さにする
		var clock = Timekeeper.instance.Clock("GroupA");
		clock.localTimeScale = 2f;
}

Group名で検索し(ここはstring) localTimescale を設定 これでGroupAのCapsuleの速度が2倍になりました

RIgidBodyのvelocityを操作する際には注意が必要になります

RIgidbodyを制御する RigidbodyTimeline3D.cs には以下のプロパティが存在します

/// <summary>
/// The velocity of the rigidbody before time effects. Use this property instead of Rigidbody.velocity, which will be overwritten by the physics timer at runtime. 
/// </summary>
public Vector3 velocity
{
	get { return bodyVelocity / timeline.timeScale; }
	set { if (AssertForwardProperty("velocity", Severity.Ignore)) bodyVelocity = value * timeline.timeScale; }
}

つまり、コード内で rigidbody.velocity を書く際には、Timescaleか考慮された RigidbodyTimeline3Dが持つvelocityを利用する必要があります。

こちらは移行マニュアルを見て各種適切に処理する必要があります

最後に

Unityでは時間制御のためにTimescaleを変更することが多いですが、全てのGameObejctが影響受けてしまうため一部のGameObjectは遅くしたくない/速くしたくない。などのケースでは Chronos を利用してプロジェクトを構築していくのが良さそうです。 コード自体もシンプルに作られているため中身を見てカスタマイズするのも容易です。

しかしTimelineを全てのGameObjectにつける必要があるため、途中から考慮するのは難しくなります また、他のサードパーティ製品ではChronosをサポートしてないケースが殆んどのため注意です